package com.hello.sandbox.utils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/** Created by qiaopu on 2018/4/26. */
public class Reflector {
  public static final String LOG_TAG = "Reflector";

  protected Class<?> mType;
  protected Object mCaller;
  protected Constructor mConstructor;
  protected Field mField;
  protected Method mMethod;

  public static Reflector on(String name) throws Exception {
    return on(name, true, Reflector.class.getClassLoader());
  }

  public static Reflector on(String name, boolean initialize) throws Exception {
    return on(name, initialize, Reflector.class.getClassLoader());
  }

  public static Reflector on(String name, boolean initialize, ClassLoader loader) throws Exception {
    try {
      return on(Class.forName(name, initialize, loader));
    } catch (Throwable e) {
      throw new Exception("Oops!", e);
    }
  }

  public static Reflector on(Class<?> type) {
    Reflector reflector = new Reflector();
    reflector.mType = type;
    return reflector;
  }

  public static Reflector with(Object caller) throws Exception {
    return on(caller.getClass()).bind(caller);
  }

  protected Reflector() {}

  public Reflector constructor(Class<?>... parameterTypes) throws Exception {
    try {
      mConstructor = mType.getDeclaredConstructor(parameterTypes);
      mConstructor.setAccessible(true);
      mField = null;
      mMethod = null;
      return this;
    } catch (Throwable e) {
      throw new Exception("Oops!", e);
    }
  }

  @SuppressWarnings("unchecked")
  public <R> R newInstance(Object... initargs) throws Exception {
    if (mConstructor == null) {
      throw new Exception("Constructor was null!");
    }
    try {
      return (R) mConstructor.newInstance(initargs);
    } catch (InvocationTargetException e) {
      throw new Exception("Oops!", e.getTargetException());
    } catch (Throwable e) {
      throw new Exception("Oops!", e);
    }
  }

  protected Object checked(Object caller) throws Exception {
    if (caller == null || mType.isInstance(caller)) {
      return caller;
    }
    throw new Exception("Caller [" + caller + "] is not a instance of type [" + mType + "]!");
  }

  protected void check(Object caller, Member member, String name) throws Exception {
    if (member == null) {
      throw new Exception(name + " was null!");
    }
    if (caller == null && !Modifier.isStatic(member.getModifiers())) {
      throw new Exception("Need a caller!");
    }
    checked(caller);
  }

  public Reflector bind(Object caller) throws Exception {
    mCaller = checked(caller);
    return this;
  }

  public Reflector unbind() {
    mCaller = null;
    return this;
  }

  public Reflector field(String name) throws Exception {
    try {
      mField = findField(name);
      mField.setAccessible(true);
      mConstructor = null;
      mMethod = null;
      return this;
    } catch (Throwable e) {
      throw new Exception("Oops!", e);
    }
  }

  protected Field findField(String name) throws NoSuchFieldException {
    try {
      return mType.getField(name);
    } catch (NoSuchFieldException e) {
      for (Class<?> cls = mType; cls != null; cls = cls.getSuperclass()) {
        try {
          return cls.getDeclaredField(name);
        } catch (NoSuchFieldException ex) {
          // Ignored
        }
      }
      throw e;
    }
  }

  @SuppressWarnings("unchecked")
  public <R> R get() throws Exception {
    return get(mCaller);
  }

  @SuppressWarnings("unchecked")
  public <R> R get(Object caller) throws Exception {
    check(caller, mField, "Field");
    try {
      return (R) mField.get(caller);
    } catch (Throwable e) {
      throw new Exception("Oops!", e);
    }
  }

  public Reflector set(Object value) throws Exception {
    return set(mCaller, value);
  }

  public Reflector set(Object caller, Object value) throws Exception {
    check(caller, mField, "Field");
    try {
      mField.set(caller, value);
      return this;
    } catch (Throwable e) {
      throw new Exception("Oops!", e);
    }
  }

  public Reflector method(String name, Class<?>... parameterTypes) throws Exception {
    try {
      mMethod = findMethod(name, parameterTypes);
      mMethod.setAccessible(true);
      mConstructor = null;
      mField = null;
      return this;
    } catch (NoSuchMethodException e) {
      throw new Exception("Oops!", e);
    }
  }

  protected Method findMethod(String name, Class<?>... parameterTypes)
      throws NoSuchMethodException {
    try {
      return mType.getMethod(name, parameterTypes);
    } catch (NoSuchMethodException e) {
      for (Class<?> cls = mType; cls != null; cls = cls.getSuperclass()) {
        try {
          return cls.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException ex) {
          // Ignored
        }
      }
      throw e;
    }
  }

  public <R> R call(Object... args) throws Exception {
    return callByCaller(mCaller, args);
  }

  @SuppressWarnings("unchecked")
  public <R> R callByCaller(Object caller, Object... args) throws Exception {
    check(caller, mMethod, "Method");
    try {
      return (R) mMethod.invoke(caller, args);
    } catch (InvocationTargetException e) {
      throw new Exception("Oops!", e.getTargetException());
    } catch (Throwable e) {
      throw new Exception("Oops!", e);
    }
  }

  public static class QuietReflector extends Reflector {

    protected Throwable mIgnored;

    public static QuietReflector on(String name) {
      return on(name, true, QuietReflector.class.getClassLoader());
    }

    public static QuietReflector on(String name, boolean initialize) {
      return on(name, initialize, QuietReflector.class.getClassLoader());
    }

    public static QuietReflector on(String name, boolean initialize, ClassLoader loader) {
      Class<?> cls = null;
      try {
        cls = Class.forName(name, initialize, loader);
        return on(cls, null);
      } catch (Throwable e) {
        //                Log.w(LOG_TAG, "Oops!", e);
        return on(cls, e);
      }
    }

    public static QuietReflector on(Class<?> type) {
      return on(type, (type == null) ? new Exception("Type was null!") : null);
    }

    private static QuietReflector on(Class<?> type, Throwable ignored) {
      QuietReflector reflector = new QuietReflector();
      reflector.mType = type;
      reflector.mIgnored = ignored;
      return reflector;
    }

    public static QuietReflector with(Object caller) {
      if (caller == null) {
        return on((Class<?>) null);
      }
      return on(caller.getClass()).bind(caller);
    }

    protected QuietReflector() {}

    public Throwable getIgnored() {
      return mIgnored;
    }

    protected boolean skip() {
      return skipAlways() || mIgnored != null;
    }

    protected boolean skipAlways() {
      return mType == null;
    }

    @Override
    public QuietReflector constructor(Class<?>... parameterTypes) {
      if (skipAlways()) {
        return this;
      }
      try {
        mIgnored = null;
        super.constructor(parameterTypes);
      } catch (Throwable e) {
        mIgnored = e;
        //                Log.w(LOG_TAG, "Oops!", e);
      }
      return this;
    }

    @Override
    public <R> R newInstance(Object... initargs) {
      if (skip()) {
        return null;
      }
      try {
        mIgnored = null;
        return super.newInstance(initargs);
      } catch (Throwable e) {
        mIgnored = e;
        //                Log.w(LOG_TAG, "Oops!", e);
      }
      return null;
    }

    @Override
    public QuietReflector bind(Object obj) {
      if (skipAlways()) {
        return this;
      }
      try {
        mIgnored = null;
        super.bind(obj);
      } catch (Throwable e) {
        mIgnored = e;
        //                Log.w(LOG_TAG, "Oops!", e);
      }
      return this;
    }

    @Override
    public QuietReflector unbind() {
      super.unbind();
      return this;
    }

    @Override
    public QuietReflector field(String name) {
      if (skipAlways()) {
        return this;
      }
      try {
        mIgnored = null;
        super.field(name);
      } catch (Throwable e) {
        mIgnored = e;
        //                Log.w(LOG_TAG, "Oops!", e);
      }
      return this;
    }

    @Override
    public <R> R get() {
      if (skip()) {
        return null;
      }
      try {
        mIgnored = null;
        return super.get();
      } catch (Throwable e) {
        mIgnored = e;
        //                Log.w(LOG_TAG, "Oops!", e);
      }
      return null;
    }

    @Override
    public <R> R get(Object caller) {
      if (skip()) {
        return null;
      }
      try {
        mIgnored = null;
        return super.get(caller);
      } catch (Throwable e) {
        mIgnored = e;
        //                Log.w(LOG_TAG, "Oops!", e);
      }
      return null;
    }

    @Override
    public QuietReflector set(Object value) {
      if (skip()) {
        return this;
      }
      try {
        mIgnored = null;
        super.set(value);
      } catch (Throwable e) {
        mIgnored = e;
        //                Log.w(LOG_TAG, "Oops!", e);
      }
      return this;
    }

    @Override
    public QuietReflector set(Object caller, Object value) {
      if (skip()) {
        return this;
      }
      try {
        mIgnored = null;
        super.set(caller, value);
      } catch (Throwable e) {
        mIgnored = e;
        //                Log.w(LOG_TAG, "Oops!", e);
      }
      return this;
    }

    @Override
    public QuietReflector method(String name, Class<?>... parameterTypes) {
      if (skipAlways()) {
        return this;
      }
      try {
        mIgnored = null;
        super.method(name, parameterTypes);
      } catch (Throwable e) {
        mIgnored = e;
        //                Log.w(LOG_TAG, "Oops!", e);
      }
      return this;
    }

    @Override
    public <R> R call(Object... args) {
      if (skip()) {
        return null;
      }
      try {
        mIgnored = null;
        return super.call(args);
      } catch (Throwable e) {
        mIgnored = e;
        //                Log.w(LOG_TAG, "Oops!", e);
      }
      return null;
    }

    @Override
    public <R> R callByCaller(Object caller, Object... args) {
      if (skip()) {
        return null;
      }
      try {
        mIgnored = null;
        return super.callByCaller(caller, args);
      } catch (Throwable e) {
        mIgnored = e;
        //                Log.w(LOG_TAG, "Oops!", e);
      }
      return null;
    }
  }

  public static Method findMethodByFirstName(Class<?> clazz, String methodName) {
    for (Method declaredMethod : clazz.getDeclaredMethods()) {
      if (methodName.equals(declaredMethod.getName())) {
        return declaredMethod;
      }
    }
    return null;
  }
}
